package cn.fuxi2022.module.pay.service.impl;

import cn.fuxi2022.commons.redis.utils.SequenceUtils;
import cn.fuxi2022.core.exception.SmartGroupException;
import cn.fuxi2022.core.utils.SmsUtils;
import cn.fuxi2022.feign.api.GroupAccounts;
import cn.fuxi2022.feign.api.GroupAuth;
import cn.fuxi2022.model.empty.applets.GroupStudentInfo;
import cn.fuxi2022.model.empty.applets.GroupStudentPayLog;
import cn.fuxi2022.model.empty.auth.GroupStudent;
import cn.fuxi2022.module.info.service.GroupStudentInfoService;
import cn.fuxi2022.module.pay.properties.WeChatPayProperties;
import cn.fuxi2022.module.pay.service.GroupStudentPayLogService;
import cn.fuxi2022.module.pay.service.WeChatPayService;
import cn.fuxi2022.rabbitmq.appletsPay.constants.WeChatPayConstants;
import cn.fuxi2022.rabbitmq.appletsPay.properties.WeChatMQProperties;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

@Slf4j
@Service
public class WeChatPayServiceImpl implements WeChatPayService {

    @Autowired
    private WxPayService wxPayService;

    @Autowired
    private SequenceUtils sequenceUtils;

    @Autowired
    private GroupStudentPayLogService groupStudentPayLogService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public Map getOrder(String openId, String id) {
        GroupStudentPayLog groupStudentPayLog = selectOrder(id);
        // 如果当前用户没有订单，就创建订单
        if (ObjectUtils.isEmpty(groupStudentPayLog)) {
            return createOrder(openId, id);
        }
        // 如果当前用户的订单已经关闭，或者订单已经超时，也重新创建订单
        if (groupStudentPayLog.getStatus() == 2 || groupStudentPayLog.getStatus() == 3) {
            return createOrder(openId, id);
        }
        // 如果当前用户的订单已经支付或退款了，就将价格设置为0，以此告诉前端该用户已经完成支付
        if (groupStudentPayLog.getStatus() == 1 || groupStudentPayLog.getStatus() == 4) {
            HashMap<String, Object> hashMap = new HashMap<>();
            hashMap.put("price", 0);
            return hashMap;
        }
        // 执行到这里，说明当前用户仍在支付中，并且订单还没有关闭
        // 将订单的参数从数据库取出，返回给前端，继续完成支付
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("order", JSONObject.parseObject(groupStudentPayLog.getAttr(), Map.class));
        hashMap.put("price", WeChatPayProperties.STUDENT_PRICE);

        return hashMap;
    }


    @Override
    public Map createOrder(String openId, String id) {
        // 1.创建订单所需信息
        // 生成订单流水号
        String orderId = sequenceUtils.getOrderId();

        // 1.发送请求至微信服务器创建订单
        WxPayUnifiedOrderRequest.WxPayUnifiedOrderRequestBuilder builder = WxPayUnifiedOrderRequest.newBuilder();
        WxPayUnifiedOrderRequest wxOrder = builder
                // 商品描述
                .body(WeChatPayProperties.SHOP_BODY)
                // 用户openid
                .openid(openId)
                // 商户订单号（使用订单流水号，当前系统自行生成上传）
                .outTradeNo(orderId)
                // 终端IP，当前用户的IP或当前服务器的IP
                .spbillCreateIp(WeChatPayProperties.SPBILL_CREATEIP)
                // 订单金额 注意：单位（分）
                .totalFee(Integer.valueOf(WeChatPayProperties.STUDENT_PRICE))
                //.totalFee(1)  测试时可以设置一分钱
                // 设置交易类型
                .tradeType(WxPayConstants.TradeType.JSAPI).build();
        // 设置签名
        wxOrder.setSignType(SIGN_TYPE);
        // 支付回调
        wxOrder.setNotifyUrl(WeChatPayProperties.NOTIFY_URL);
        // 获取响应
        WxPayMpOrderResult order;
        try {
            order = wxPayService.createOrder(wxOrder);
        } catch (WxPayException e) {
            e.printStackTrace();
            throw new SmartGroupException(e.getMessage());
        }

        // 将前端微信支付所需参数转化为JSON
        String jsonString = JSONObject.toJSONString(order);

        // 2.数据库构建订单对象
        GroupStudentPayLog payLog = new GroupStudentPayLog();
        payLog.setStudentId(id);
        payLog.setOrderNo(orderId);
        payLog.setPayType(1);
        payLog.setStatus(0);
        payLog.setTotalFee(new BigDecimal(WeChatPayProperties.STUDENT_PRICE));
        payLog.setAttr(jsonString);
        // 保存订单对象
        groupStudentPayLogService.save(payLog);

        // 将订单发送至MQ，执行订单超时与提醒
        // 发送消息至交换机，没有设置路由键，将消息复制多份，发送至所有队列
        rabbitTemplate.convertAndSend(WeChatPayConstants.GROUP_ORDER_TTL_EXCHANGE, null, payLog.getOrderNo());

        // 3.将构建好的对象转化为Map集合
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("order", JSONObject.parseObject(jsonString, Map.class));
        hashMap.put("price", WeChatPayProperties.STUDENT_PRICE);
        return hashMap;
    }


    @Override
    public GroupStudentPayLog selectOrder(String studentId) {
        // 1.获取当前用户的订单
        // 一个用户有多个订单，订单有成功的，也可能有失败的
        // 但是成功一定是最后一个的！！！，因此查询最后一个即可
        LambdaQueryWrapper<GroupStudentPayLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(GroupStudentPayLog::getStudentId, studentId)
                // 按照时间倒序排序，越晚创建的订单越考前
                .orderByDesc(GroupStudentPayLog::getGmtCreate)
                .last("limit 1");
        GroupStudentPayLog payLog = groupStudentPayLogService.getOne(wrapper);

        // 2.如果订单还在支付中，就更新订单
        if (ObjectUtils.isNotEmpty(payLog) && payLog.getStatus() == 0) {
            checkOrder(payLog.getOrderNo());
            // 再次查询
            payLog = groupStudentPayLogService.getOne(wrapper);
        }

        // 3.返回订单对象
        return payLog;
    }

    @Autowired
    private GroupStudentInfoService groupStudentInfoService;

    @Autowired
    private SmsUtils smsUtils;

    @Override
    public void checkOrder(String orderNo) {

        // 根据系统生成的流水号请求微信服务器查询订单
        WxPayOrderQueryResult queryResult;
        try {
            queryResult = wxPayService.queryOrder(null, orderNo);
        } catch (WxPayException e) {
            e.printStackTrace();
            throw new SmartGroupException(e.getMessage());
        }

        // 如果当前订单仍在支付中，就不需要进行任何操作，继续等待支付
        if ("NOTPAY".equals(queryResult.getTradeState())) {
            // 执行到这里，说明当前用户还没有付款，订单更新结束，继续等待用户付款
            return;
        }

        // 查询订单，查询出来后才能更新
        LambdaQueryWrapper<GroupStudentPayLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(GroupStudentPayLog::getOrderNo, orderNo);
        GroupStudentPayLog payLog = groupStudentPayLogService.getOne(wrapper);

        // 如果微信查询的订单为成功，就更新数据库
        if ("SUCCESS".equals(queryResult.getTradeState())) {
            // 设置订单状态
            payLog.setStatus(1);
            // 当前社员已经完成支付社费，修改入社程序
            LambdaQueryWrapper<GroupStudentInfo> groupStudentInfoWrapper = new LambdaQueryWrapper<>();
            groupStudentInfoWrapper.eq(StringUtils.hasText(payLog.getStudentId()), GroupStudentInfo::getStudentId, payLog.getStudentId());
            GroupStudentInfo studentInfo = groupStudentInfoService.getOne(groupStudentInfoWrapper);
            studentInfo.setIsStudent(2);
            groupStudentInfoService.updateById(studentInfo);
            // 记录订单成功时间
            payLog.setPayTime(LocalDateTime.parse(queryResult.getTimeEnd(), TIME_END_FORMATTER));
            // 发送订单成功消息
            pushMessage(payLog);

        } else if ("CLOSED".equals(queryResult.getTradeState()) && payLog.getStatus() == 0) {
            // 如果当前微信订单已经取消，但是数据库订单仍在支付中，就更新订单状态
            // 理论订单超时关闭时间
            long timeOut = payLog.getGmtCreate().getTime() + WeChatMQProperties.GROUP_ORDER_TTL_ORDER_QUEUE_TIME;
            // 当前系统时间
            long currentTimeMillis = System.currentTimeMillis();
            // 判断订单关闭原因
            // 系统任务，订单创建时间+订单超时时间 后的时间，再加上5秒订单关闭时间，
            // 也就是说，在订单超时时间后的内三秒查询订单状态，那么它就是超时关闭，其他都为手动关闭
            if (timeOut <= currentTimeMillis && (timeOut + 1000 * 5) >= currentTimeMillis) {
                payLog.setStatus(3);
            } else {
                payLog.setStatus(2);
            }
            // 设置订单关闭时间
            payLog.setPayTime(LocalDateTime.now());
        } else if ("REFUND".equals(queryResult.getTradeState()) && payLog.getStatus() == 1) {
            // 如果当前微信订单已经退款，但是数据库订单仍在支付成功中，就更新订单状态
            payLog.setStatus(4);
        }

        // 保存订单信息
        payLog.setTransactionId(queryResult.getTransactionId());
        payLog.setTradeState(queryResult.getTradeState());
        payLog.setAttr(JSON.toJSONString(queryResult));
        groupStudentPayLogService.updateById(payLog);
    }

    @Override
    public String payCallback(HttpServletRequest request) {
        // 1.解析回调传过来的参数
        String xmlResult;
        WxPayOrderNotifyResult payResult;
        try {
            xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
            payResult = wxPayService.parseOrderNotifyResult(xmlResult);
        } catch (IOException | WxPayException e) {
            throw new RuntimeException(e);
        }
        // 2、回调参数的 订单流水号ID + 商户号订单ID
        String orderId = payResult.getOutTradeNo();
        // String wxTransactionId = payResult.getTransactionId();

        // 3.请求微信服务器，查询订单
        checkOrder(orderId);

        return WxPayNotifyResponse.success("处理成功!");
    }

    @Override
    public void closeOrder(String orderNo) {
        // 检查订单是否完成，完成就不需要关闭
        LambdaQueryWrapper<GroupStudentPayLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(GroupStudentPayLog::getOrderNo, orderNo);
        GroupStudentPayLog payLog = groupStudentPayLogService.getOne(wrapper);

        // 判断订单状态
        if (payLog.getStatus() != 0) {
            // 如果订单状态不是在支付中，就说明订单已经完成或者关闭
            return;
        }

        // 关闭订单
        try {
            // 发送请求，关闭订单
            wxPayService.closeOrder(payLog.getOrderNo());
            // 更新订单信息
            checkOrder(payLog.getOrderNo());
        } catch (WxPayException e) {
            e.printStackTrace();
            throw new SmartGroupException(e.getMessage());
        }

        payLog = groupStudentPayLogService.getOne(wrapper);

        if (payLog.getStatus() == 2 || payLog.getStatus() == 3) {
            // 发送消息
            pushMessage(payLog);
        }
    }

    @Override
    public void sendOrderMsg(String orderNo) {
        // 检查订单是否完成，完成就不需要关闭
        LambdaQueryWrapper<GroupStudentPayLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(GroupStudentPayLog::getOrderNo, orderNo);
        GroupStudentPayLog payLog = groupStudentPayLogService.getOne(wrapper);

        // 判断订单状态
        if (payLog.getStatus() != 0) {
            // 如果订单状态不是在支付中，就说明订单已经完成或者关闭
            return;
        }

        // 获取订单的创建时间
        long createTime = payLog.getGmtCreate().getTime();
        // 获取当前时间
        long now = System.currentTimeMillis();

        //如果 当前时间 比 订单创建时间 + 订单超时时间 大
        // 说明订单已经超时，无需提醒
        if (now > createTime + WeChatMQProperties.GROUP_ORDER_TTL_ORDER_QUEUE_TIME) {
            return;
        }

        // 发送消息提醒用户
        pushMessage(payLog);
        // log.info("提醒用户超时{}", studentId);

        // 判断是否还需要再次提醒
        // 判断是否还需要再次提醒
        if (now + WeChatMQProperties.GROUP_ORDER_TTL_ORDER_ALERT_TIME >= (payLog.getGmtCreate().getTime() + WeChatMQProperties.GROUP_ORDER_TTL_ORDER_QUEUE_TIME - 1000 * 50)) {
            // 如果当前时间+超时提醒时间 比 订单超时时间减去60秒 大，就不需要再提醒了
            // log.info("距离订单超时不足一分钟，不再提醒");
            return;
        }

        // 该订单需要再次提醒，重新投入交换机
        // 将订单发送至MQ，执行订单超时与提醒
        // 发送消息至交换机
        rabbitTemplate.convertAndSend(WeChatPayConstants.GROUP_ORDER_TTL_EXCHANGE, null, orderNo);
    }

    @Autowired
    private GroupAuth groupAuth;

    @Autowired
    private GroupAccounts groupAccounts;


    SimpleDateFormat DATA_FORMAT = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss", Locale.getDefault());

    private void pushMessage(GroupStudentPayLog payLog) {
        GroupStudent student = groupAuth.getStudent(payLog.getStudentId());

        Date time;
        if (payLog.getStatus() == 0) {
            time = payLog.getGmtCreate();
        } else {
            time = Date.from(payLog.getPayTime().atZone(ZoneId.systemDefault()).toInstant());
        }

        String price = payLog.getTotalFee().divide(new BigDecimal(100)).toString();


        HashMap<String, Object> map = new HashMap<>();
        map.put("openId", student.getOpenidGz());
        map.put("name", WeChatPayProperties.SHOP_BODY);
        map.put("orderNo", payLog.getOrderNo());
        map.put("price", price);
        map.put("time", DATA_FORMAT.format(time));
        map.put("type", payLog.getStatus());

        // 远程调用公众号服务，发送模板消息，订单成功
        groupAccounts.pushMessage(map);

        if (payLog.getStatus() == 1) {
            // 发送短信，订单成功
            smsUtils.sendOrderSuccess(student.getMobile(), price, payLog.getOrderNo(), DATA_FORMAT.format(time));
        }
    }

    @Override
    public void refundOrder(String orderNo) {
        // 1.获取订单对象
        LambdaQueryWrapper<GroupStudentPayLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(GroupStudentPayLog::getOrderNo, orderNo);
        GroupStudentPayLog payLog = groupStudentPayLogService.getOne(wrapper);

        // 2.判断订单状态
        if (payLog.getStatus() != 1) {
            throw new SmartGroupException("非法的订单退款");
        }

        // 3.构建关闭订单请求参数
        WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();
        // 订单流水号
        wxPayRefundRequest.setOutTradeNo(payLog.getOrderNo());
        // 订单退款号设置为订单流水号
        wxPayRefundRequest.setOutRefundNo(payLog.getOrderNo());
        // 退款原因
        wxPayRefundRequest.setRefundDesc(WeChatPayProperties.REFUND_DESC);
        // 退款金额
        wxPayRefundRequest.setRefundFee(Integer.parseInt(String.valueOf(payLog.getTotalFee().setScale(0, RoundingMode.DOWN))));
        // 退款的订单总金额
        wxPayRefundRequest.setTotalFee(Integer.parseInt(String.valueOf(payLog.getTotalFee().setScale(0, RoundingMode.DOWN))));

        // 4.发送请求，开始退款
        try {
            wxPayService.refundV2(wxPayRefundRequest);
        } catch (WxPayException e) {
            e.printStackTrace();
            throw new SmartGroupException("订单退款失败");
        }

        // 5.执行到这里，订单退款成功
        // 查询订单信息，更新订单信息
        checkOrder(orderNo);
    }
}
